package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.UAFCommonRuntimeException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFDeregisterProcessor;
import com.gmrz.uaf.protocol.v1.processor.UAFUpdateCertStatusProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertAuthFinishProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertAuthInitProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertRegFinishProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertRegInitProcessor;
import com.gmrz.uaf.server.ServerConfig;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.*;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.util.*;

import static com.gmrz.uaf.common.Constants.CANCEL_REASON_TYPE_6;

/**
 *
 */
public class UAFUpdateCertHandler extends UAFBaseHandler {

    private static final Logger LOG = LogManager.getLogger(UAFUpdateCertHandler.class);


    /**
     * @param request
     */
    public String updateCert(String request) {

        LOG.info("[/cert/update][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();
        String response = null;
        try {


            Gson gson = UAFSchemaBuilder.getGsonProcessCert();
            Type reqType = new TypeToken<ReceiveReqPayload<UpdateCertContext>>() {
            }.getType();

            ReceiveReqPayload<UpdateCertContext> reqPayload = gson.fromJson(request, reqType);
            UpdateCertContext context = reqPayload.getContext();
            logRequest(request, reqPayload.getContext().getTransNo(), context.getUserName(),
                    context.getAppID(), context.getAuthType(),context.getTransType(), Constants.Method.UPDATE_CERT);
            validate(reqPayload);
            String custNo = context.getUserName();
            String hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            String appID = context.getAppID();
            String deviceID = context.getDevices().getDeviceID();
            String authType = context.getAuthType();
            String transType = context.getTransType();
            String authExt = context.getAuthExt();
            UAPCertAuthInitProcessor authInitProcessor = GuiceUtil.getProcessorInjector().
                    getInstance(UAPCertAuthInitProcessor.class);
            if(Strings.isNullOrEmpty(reqPayload.getUafResponse())){
                List<String> authTypeList = new ArrayList<String>();
                authTypeList.add(authType);
                List<AuthenticationRequest> authenticationRequests =  authInitProcessor.request(custNo, hashedUserName, appID, deviceID, authTypeList, transType, null, null, authExt);
                response = buildUpdateCertReceiveResponse(gson, respPayload, authenticationRequests);
                String challenge = Convert.toBase64(authenticationRequests.get(0).getOperationHeader().getServerData().getChallenge());
                saveContextToRedis(context, challenge, hashedUserName);
                setUpdateCertStep(hashedUserName,challenge, Constants.UPDATE_CERT_STEP_1);
                LOG.info("[/cert/update][end]");
                return response;
            }else {
                String uafResponse = reqPayload.getUafResponse();
                Type type = new com.google.common.reflect.TypeToken<List<HeaderAndFinalChallenge>>() {
                }.getType();
                List<HeaderAndFinalChallenge> headerAndFinalChallenges = gson.fromJson(uafResponse, type);
                String challenge = Convert.toBase64(headerAndFinalChallenges.get(0).getFinalChallengeParams().getServerChallenge().getChallenge());
                String step = getUpdateCertStep(hashedUserName,challenge);
                if(Strings.isNullOrEmpty(step)){
                    throw new UAFCommonRuntimeException(UAFErrorCode.PROTOCOL_UPDATE_CERT_TIMEOUT_FAILED);
                }
                validate(context,challenge ,hashedUserName);
                if(Constants.UPDATE_CERT_STEP_1.equals(step)){
                    Type AuthenticationResponseType = new com.google.common.reflect.TypeToken<List<AuthenticationResponse>>() {
                    }.getType();
                    List<AuthenticationResponse> authResponses = gson.fromJson(uafResponse, AuthenticationResponseType);
                    //获取当前版本的response参数
                    AuthenticationResponse authResponse = null;
                    if ((authResponses != null) && (!authResponses.isEmpty())) {
                        if (authResponses.size() > 1) {
                            authResponse = RestValidator.compareAuthRespVersions(authResponses);
                        } else {
                            authResponse = authResponses.get(0);
                        }
                    }
                    if (null == authResponse) {
                        LOG.error("uafResponse is Null");
                        throw new PayloadValidationException("uafResponse", RestValidator.VALIDATION_ERROR.EMPTY);
                    }
                    UAPCertAuthFinishProcessor uapCertAuthFinishProcessor = GuiceUtil.getProcessorInjector().getInstance(UAPCertAuthFinishProcessor.class);
                    AuthenticationAssertion assertion = authResponse.getAuthenticatorAssertions().get(0);
                    String assertionAAID = assertion.getSignResponse().getSignData().getAAID();
                    AuthenticatorSpec spec = assertionAAID == null ? null : uapCertAuthFinishProcessor.getAuthenticatorSpec(assertionAAID);
                    if(null != spec){
                        assertion.getSignResponse().getSignData().withSignAlgorithm(spec.getAlgorithm());
                    }
                    setSignatureType(hashedUserName,challenge);
                    uapCertAuthFinishProcessor.process(authResponse, context.getAppID(), hashedUserName, reqPayload.getContext().getAuthType(), reqPayload.getContext().getTransType(),null,null);

                    UAPCertRegInitProcessor uapCertRegInitProcessor = GuiceUtil.getProcessorInjector().getInstance(UAPCertRegInitProcessor.class);
                    List<RegistrationRequest> requests = uapCertRegInitProcessor.request(custNo, hashedUserName, context.getAppID(),
                            context.getDevices().getDeviceID(), context.getAuthType(), context.getTransType(), null,context.getDn());
                    //用户名是明文返回还是密文返回
                    if (!GuiceUtil.getProcessorInjector().getInstance(ServerConfig.class).isSecurity()) {
                        for (RegistrationRequest rr : requests) {
                            rr.setUsername(context.getUserName());
                        }
                    }
                    for (RegistrationRequest rr : requests) {
                        List<MatchCriteria> disallowed = rr.getPolicy().getDisallowed();
                        List<List<MatchCriteria>> accepted = rr.getPolicy().getAccepted();
                        for(MatchCriteria matchCriteria : disallowed){
                            MatchCriteria m = new MatchCriteria();
                            m.setAAID(matchCriteria.getAAID());
                            accepted.add(Collections.singletonList(m));
                        }
                        rr.getPolicy().setDisallowed(null);
                    }

                    response = buildUpdateCertReceiveResponse(gson, respPayload, requests);
                    String challengeReg = Convert.toBase64(requests.get(0).getHeader().getServerData().getChallenge());
                    saveContextToRedis(context, challengeReg, hashedUserName);
                    setUpdateCertStep(hashedUserName,challengeReg, Constants.UPDATE_CERT_STEP_2);
                    LOG.info("[/cert/update][end]");
                    return response;
                }else if(Constants.UPDATE_CERT_STEP_2.equals(step)){
                    Type RegistrationResponseType = new com.google.common.reflect.TypeToken<List<RegistrationResponse>>() {
                    }.getType();
                    List<RegistrationResponse> regResponses = gson.fromJson(uafResponse, RegistrationResponseType);
                    //获取当前版本的response参数
                    RegistrationResponse regResponse = null;
                    if ((regResponses != null) && (!regResponses.isEmpty())) {
                        if (regResponses.size() > 1) {
                            regResponse = RestValidator.compareRegRespVersions(regResponses);
                        } else {
                            regResponse = regResponses.get(0);
                        }
                    }
                    if (null == regResponse) {
                        LOG.error("RegResponse is Null");
                        throw new PayloadValidationException("UAFRegResponse", RestValidator.VALIDATION_ERROR.EMPTY);
                    }
                    UAPCertRegFinishProcessor uapCertRegFinishProcessor = GuiceUtil.getProcessorInjector().getInstance(UAPCertRegFinishProcessor.class);
                    RegistrationAssertion assertion = regResponse.getAssertions().get(0);
                    String assertionAAID = assertion.getAuthenticatorResponse().getKRD().getAAID();
                    AuthenticatorSpec spec = assertionAAID == null ? null : uapCertRegFinishProcessor.getAuthenticatorSpec(assertionAAID);
                    if(null != spec){
                        assertion.getAuthenticatorResponse().getKRD().setSignAlgorithm(spec.getAlgorithm());
                        assertion.getAuthenticatorResponse().getKRD().setKeyFormat(spec.getKeyFormat());
                        assertion.getAuthenticatorResponse().setAttestationType(spec.getAttestationTypesAsList().get(0));
                    }
                    RegSendContext regSendContext = new RegSendContext();

                    regSendContext.setAppID(context.getAppID());
                    regSendContext.setAuthType(context.getAuthType());
                    regSendContext.setTransType(context.getTransType());
                    regSendContext.setDevices(context.getDevices());
                    regSendContext.setMobile(context.getMobile());
                    regSendContext.setRf1(context.getRf1());
                    regSendContext.setRf2(context.getRf2());
                    regSendContext.setExt(context.getRegExt());
                    regSendContext.setCustType(context.getCustType());
                    regSendContext.setRegFrom(context.getRegFrom());
                    //regSendContext.setInstCode(context.getInstCode());
                    if(com.gmrz.util.Strings.isNullOrEmpty(context.getInstCode())){
                        context.setInstCode(context.getCustType());
                    }

                    UAFRegFinishResultBean requests = uapCertRegFinishProcessor.process(regResponse, hashedUserName,regSendContext);
                    UAFDeregisterProcessor deregisterProcessor = GuiceUtil.getProcessorInjector().getInstance(UAFDeregisterProcessor.class);
                    DeregisterationRequest deregisterationRequest = deregisterProcessor.deleteOthers(hashedUserName, context.getAppID(),
                            context.getDevices().getDeviceID(), context.getAuthType(), context.getTransType(), "00",CANCEL_REASON_TYPE_6,requests.getUpdateAuthRequest().getPolicy().getAccepted().get(0).get(0).getKeyIDList().get(0).toString());

                    UAFUpdateCertStatusProcessor processor = GuiceUtil.getProcessorInjector().
                            getInstance(UAFUpdateCertStatusProcessor.class);
                    int n = processor.updateCertActivityStatus(context.getAppID(),hashedUserName,requests.getUpdateAuthRequest().getPolicy().getAccepted().get(0).get(0).getKeyIDList().get(0).toString());

                    if (null != requests && null != requests.getDeregisterationRequest()) {
                        // 本地认证才返回服务器注销报文
                        if (requests.getDeregisterationRequest().getDeregisterAuthenticators() != null && !requests.getDeregisterationRequest().getDeregisterAuthenticators().isEmpty()) {
                            List<DeregisterAuthenticator> deregisterationRequests = requests.getDeregisterationRequest().getDeregisterAuthenticators();
                            if(null != deregisterationRequests){
                                deregisterationRequest.getDeregisterAuthenticators().addAll(deregisterationRequests);
                            }

                        }
                    }

                    String uafDeleteRequest = gson.toJson(Collections.singletonList(deregisterationRequest));
                    Extension extension = new Extension();
                    extension.setId("uafDeleteRequest");
                    extension.setData(Convert.toBase64(uafDeleteRequest.getBytes()));
                    List<Extension> listExtension = requests.getUpdateAuthRequest().getOperationHeader().getExtensions();
                    if(null != listExtension) {
                        listExtension.add(extension);
                    }else {
                        listExtension = Collections.singletonList(extension);
                    }
                    requests.getUpdateAuthRequest().getOperationHeader().setExtensions(listExtension);

                    List<UpdateAuthRequest> list = Collections.singletonList(requests.getUpdateAuthRequest());
                    String uafRequest = gson.toJson(list);
                    Map<String,String> exts = new HashMap<String, String>();
                    exts.put("keyID",requests.getUpdateAuthRequest().getPolicy().getAccepted().get(0).get(0).getKeyIDList().get(0).toString());
                    respPayload.setExts(exts);
                    respPayload.setUafRequest(uafRequest);
                    respPayload.setStatusCode(Constants.StatusCode.OK.getCode());
                    respPayload.setDescription("OK");
                    response = gson.toJson(respPayload);
                    LOG.info("[/cert/update][end]");
                    return response;
                }
            }
        } catch (Exception ex) {
            LOG.error(ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/cert/update][end]");
        return response;
    }



    private void validate(ReceiveReqPayload<UpdateCertContext> reqPayload)
            throws PayloadValidationException {
        if ((reqPayload == null) || (reqPayload.getContext() == null)) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        RestValidator.validate("transNo", reqPayload.getContext().getTransNo());
        RestValidator.validate("userName", reqPayload.getContext().getUserName());
        RestValidator.validate("rf1", reqPayload.getContext().getRf1());
        RestValidator.validate("rf2", reqPayload.getContext().getRf2());
        RestValidator.validate("appID", reqPayload.getContext().getAppID());
        //RestValidator.validate("keyID", reqPayload.getContext().getKeyID());
    }


    /**
     * 缓存发起接口的 context 参数
     * @param context
     * @param hashedUserName
     * @throws ChallengeStoreException
     */
    private void saveContextToRedis(UpdateCertContext context,String challenge , String hashedUserName) throws ChallengeStoreException {
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_UPDATE_CERT_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName, this.getServerConfig().getChallengeValiditySeconds(),context.toString());
    }
    private void setSignatureType(String hashedUserName, String challenge) throws ChallengeStoreException{
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_SIGNATURE_TYPE_CACHE_KEY + challenge + "-" + hashedUserName, this.getServerConfig().getChallengeValiditySeconds(),"1");

    }
    void validate(UpdateCertContext context,String challenge , String hashedUserName) throws ChallengeStoreException, PayloadValidationException {
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        String paramCache = challengeStoreService.getRedisValue(Constants.UAP_UPDATE_CERT_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName);
        if (null == paramCache || "".equals(paramCache.trim())) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
        } else {
            String param = context.toString();
            LOG.debug("update cert context param:" + param + "---cache:" + paramCache);
            if (!param.equals(paramCache)) {
                LOG.error("update cert context param:" + param + "---cache:" + paramCache);
                throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
            }
        }
    }

    private void setUpdateCertStep(String hashedUserName, String challenge,String step) throws ChallengeStoreException {
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_UPDATE_CERT_STEP_CACHE_KEY + challenge + "-" + hashedUserName, this.getServerConfig().getChallengeValiditySeconds() , step);
    }

    private String getUpdateCertStep(String hashedUserName, String challenge) throws ChallengeStoreException {
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        return challengeStoreService.getRedisValue(Constants.UAP_UPDATE_CERT_STEP_CACHE_KEY + challenge + "-" + hashedUserName);
    }
}


